import pandas as pd
from datetime import datetime
import backtrader as bt
import matplotlib.pyplot as plt
import akshare as ak

plt.rcParams["font.sans-serif"] = ["SimHei"] #设置画图时的中文显示
plt.rcParams["axes.unicode_minus"] = False #设置画图时的负号显示


# 1.数据加载
def get_data(code="600519", starttime="20210501", endtime="20240531") -> pd.DataFrame:
    df = ak.stock_zh_a_hist(symbol=code, period="daily",
                            start_date=starttime, end_date=endtime, adjust="qfq")

    column_mapping = {
        '日期': 'date',
        '开盘': 'open',
        '收盘': 'close',
        '最高': 'high',
        '最低': 'low',
        '成交量': 'volume',
    }
    df.rename(columns=column_mapping, inplace=True)
    df["openinterest"] = 0
    df["date"] = pd.to_datetime(df['date'], utc=True).dt.tz_localize(None)
    df.index = pd.to_datetime(df.date)
    # 对df的数据进行一个整合
    df = df[["open", "high", "low", "close", "volume", "openinterest"]]
    print(df)
    return df


stock_df = get_data()
# 加载并读取数据源 dataname（数据来源） fromdate（date日期）todate（截止时间）
fromdate = datetime(2021, 5, 1).date()
todate = datetime(2024, 5, 31).date()

data = bt.feeds.PandasData(dataname=stock_df, fromdate=fromdate, todate=todate)


# 2.构建策略
# 上穿20日线买入，跌穿20日均线就卖出
class TrendStrategy(bt.Strategy):
    params = (
        ("maperiod", 20),
    )

    def __init__(self):
        self.order = None
        self.ma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)

    # 每个bar都会执行一次，回测的每个日期都回执行一次
    def next(self):
        # 判断是否有交易指令正在执行
        if self.order:
            return

        # 空仓
        if not self.position:
            if self.datas[0].close[0] > self.ma[0]:
                self.order = self.buy(size=200)
            if self.datas[0].close[0] < self.ma[0]:
                self.order = self.sell(size=200)


# 3. 策略设置
# 创建大脑
cerebro = bt.Cerebro()
# 讲数据加入回测系统
cerebro.adddata(data)
# 加入自定义的策略
cerebro.addstrategy(TrendStrategy)
# 经纪人
startcash = 500000
cerebro.broker.setcash(startcash)
# 充值
# cerebro.broker.add_cash(100000)
# 设置手续费
cerebro.broker.setcommission(0.0002)
# 滑点管理
# 方式1，通过slip_perc参数设置百分比滑点
# cerebro.broker = bt.brokers.BackBroker(perc=0.0001)
# 方式2，通过brokers的set_slippage_perc方法设置百分比滑点
cerebro.broker.set_slippage_perc(perc=1)

# 4. 执行回测
s = fromdate.strftime("%Y-%m-%d")
t = todate.strftime("%Y-%m-%d")
print(f"初始资金：{startcash}\n回测时间：{s}-{t}")
cerebro.run()
portval = cerebro.broker.getvalue()
print(f"剩余总资金：{portval}\n回测时间：{s}-{t}")


# 交易时间模式：Cheat-On-Open和Cheat-On-Close
# 默认是“当日收盘价后下单，次日以开盘价成交”，这种模式在回测过程中能有效避免使用未来数据：
# 方式1：bt.Cerebro(cheat_on_open=True)
# 方式2（一般采用）：cerebro.broker.set_coo(True)
# 方式3：BackBoroker(coo=True)

# 交易
# buy：买入、做多 long
# sell：卖出、做空 short
# close：平仓 cover
# ------
# price：订单委托价
# size：订单委托数量

# 按照目标数量下单
# 按照目标数量下单 self.order = self.order_target_size(target=size)
# 按照目标金额下单 self.order = self.order_target_value(target=size)
# 按照目标百分比下单 self.order = self.order_target_percent(target=size)
